#!/usr/bin/env node
import puppeteer from "puppeteer";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const DEFAULT_URL = process.env.MCP_DEV_URL ?? "http://localhost:5173/";
const CHROME_PATH = process.env.MCP_CHROME_PATH ?? process.env.PUPPETEER_EXECUTABLE_PATH;
const NAV_TIMEOUT = Number(process.env.MCP_NAV_TIMEOUT ?? "45000");
const WAIT_TIMEOUT = Number(process.env.MCP_WAIT_TIMEOUT ?? "45000");
const MAX_LOG_ENTRIES = 500;
const MAX_SCREENSHOTS = 8;

const state = {
  consoleLogs: [],
  screenshots: new Map(),
  browser: null,
  page: null,
  listenersAttached: false,
  grantedPermissions: new Set(),
};

const AVAILABLE_PERMISSIONS = new Map([
  ["status", "Read current page metadata and console logs"],
  ["navigate", "Navigate to arbitrary URLs"],
  ["reload", "Reload the current page"],
  ["screenshot", "Capture screenshots of the page"],
  ["console_clear", "Clear stored console log entries"],
  ["eval", "Execute custom JavaScript within the page"],
  ["wait_selector", "Wait for DOM elements matching a selector"],
  ["click", "Click elements within the page"],
  ["type", "Type text into elements"],
  ["scroll", "Scroll the page viewport"],
  ["set_viewport", "Adjust browser viewport dimensions"],
]);

function normalizePermission(name) {
  return String(name ?? "")
    .trim()
    .toLowerCase();
}

function expandPermissionInputs(names) {
  const normalized = new Set();
  for (const raw of names ?? []) {
    const value = normalizePermission(raw);
    if (!value) {
      continue;
    }
    if (value === "all" || value === "*") {
      for (const key of AVAILABLE_PERMISSIONS.keys()) {
        normalized.add(key);
      }
      continue;
    }
    if (!AVAILABLE_PERMISSIONS.has(value)) {
      throw new Error(`Unknown permission: ${value}`);
    }
    normalized.add(value);
  }
  if (normalized.size === 0) {
    throw new Error("No valid permissions provided");
  }
  return Array.from(normalized.values()).sort();
}

function hasPermission(name) {
  return state.grantedPermissions.has(name);
}

function requirePermission(name) {
  if (!hasPermission(name)) {
    throw new Error(`Permission "${name}" not granted. Grant it via dev_grant_permissions.`);
  }
}

function permissionStatusReport() {
  const lines = [];
  for (const [name, description] of AVAILABLE_PERMISSIONS) {
    const marker = hasPermission(name) ? "granted" : "denied";
    lines.push(`[${marker}] ${name} - ${description}`);
  }
  return lines.join("\\n");
}

function serializeForOutput(value) {
  if (value === undefined) {
    return "undefined";
  }
  if (value === null) {
    return "null";
  }
  if (typeof value === "string") {
    return value;
  }
  if (typeof value === "number" && !Number.isFinite(value)) {
    return value.toString();
  }
  if (typeof value === "function") {
    return value.toString();
  }
  try {
    return JSON.stringify(value, null, 2);
  } catch (_error) {
    return String(value);
  }
}

const server = new Server(
  {
    name: "dev-chrome-monitor",
    version: "0.1.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
    },
  },
);

function notifyConsoleUpdate() {
  try {
    server.notification({
      method: "notifications/resources/updated",
      params: { uri: "console://logs" },
    });
  } catch (error) {
    // Swallow notifications issued before transport is ready
  }
}

function notifyScreenshotUpdate() {
  try {
    server.notification({
      method: "notifications/resources/list_changed",
      params: {},
    });
  } catch (error) {
    // Swallow notifications issued before transport is ready
  }
}

function pushLog(kind, text) {
  const entry = `${new Date().toISOString()} [${kind}] ${text}`;
  state.consoleLogs.push(entry);
  if (state.consoleLogs.length > MAX_LOG_ENTRIES) {
    state.consoleLogs.splice(0, state.consoleLogs.length - MAX_LOG_ENTRIES);
  }
  notifyConsoleUpdate();
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function waitForUrl(url, timeoutMs) {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    try {
      const response = await fetch(url, { method: "GET" });
      if (response.ok || response.status === 404) {
        return true;
      }
    } catch (error) {
      // Ignore connection failures until timeout
    }
    await delay(1500);
  }
  return false;
}

function attachPageListeners(page) {
  if (state.listenersAttached) {
    return;
  }
  page.on("console", (message) => {
    pushLog(`console:${message.type()}`, message.text());
  });
  page.on("pageerror", (error) => {
    pushLog("pageerror", error.message ?? String(error));
  });
  page.on("requestfailed", (request) => {
    const failure = request.failure();
    pushLog(
      "requestfailed",
      `${request.method()} ${request.url()} ${failure ? failure.errorText : "unknown"}`,
    );
  });
  page.on("response", (response) => {
    if (response.status() >= 400) {
      pushLog("response", `${response.status()} ${response.url()}`);
    }
  });
  state.listenersAttached = true;
}

async function ensurePage() {
  if (!state.browser) {
    pushLog("info", "Launching Chrome instance for dev monitoring");
    state.browser = await puppeteer.launch({
      headless: false,
      executablePath: CHROME_PATH,
      defaultViewport: null,
      args: [
        "--disable-web-security",
        "--disable-features=IsolateOrigins,site-per-process",
        "--disable-site-isolation-trials",
        "--no-default-browser-check",
        "--no-first-run",
      ],
    });
    const pages = await state.browser.pages();
    state.page = pages.length > 0 ? pages[0] : await state.browser.newPage();
    attachPageListeners(state.page);
  }
  if (!state.page || state.page.isClosed()) {
    const pages = await state.browser.pages();
    state.page = pages.length > 0 ? pages[0] : await state.browser.newPage();
    attachPageListeners(state.page);
  }
  return state.page;
}

async function autoNavigate() {
  const page = await ensurePage();
  const targetUrl = DEFAULT_URL;
  const ready = await waitForUrl(targetUrl, WAIT_TIMEOUT);
  if (!ready) {
    pushLog(
      "warn",
      `Dev server not reachable at ${targetUrl}. Start it and call dev_navigate when ready.`,
    );
    return;
  }
  try {
    await page.goto(targetUrl, { waitUntil: "networkidle0", timeout: NAV_TIMEOUT });
    pushLog("info", `Navigated to ${targetUrl}`);
  } catch (error) {
    pushLog("error", `Initial navigation failed: ${error.message ?? error}`);
  }
}

function listResources() {
  const resources = [];
  if (hasPermission("status")) {
    resources.push({
      uri: "console://logs",
      mimeType: "text/plain",
      name: "Dev server console",
    });
  }
  if (hasPermission("screenshot")) {
    for (const name of state.screenshots.keys()) {
      resources.push({
        uri: `screenshot://${name}`,
        mimeType: "image/png",
        name: `Screenshot: ${name}`,
      });
    }
  }
  return { resources };
}

function readResource(uri) {
  if (uri === "console://logs") {
    if (!hasPermission("status")) {
      throw new Error('Permission "status" not granted. Grant it via dev_grant_permissions.');
    }
    return {
      contents: [
        {
          uri,
          mimeType: "text/plain",
          text: state.consoleLogs.join("\n"),
        },
      ],
    };
  }
  if (uri.startsWith("screenshot://")) {
    if (!hasPermission("screenshot")) {
      throw new Error('Permission "screenshot" not granted. Grant it via dev_grant_permissions.');
    }
    const name = uri.split("://")[1];
    const buffer = state.screenshots.get(name);
    if (buffer) {
      return {
        contents: [
          {
            uri,
            mimeType: "image/png",
            blob: buffer,
          },
        ],
      };
    }
  }
  throw new Error(`Resource not found: ${uri}`);
}

function registerHandlers() {
  server.setRequestHandler(ListResourcesRequestSchema, async () => listResources());
  server.setRequestHandler(ReadResourceRequestSchema, async (request) =>
    readResource(request.params.uri.toString()),
  );
  async function recreatePage() {
    try {
      if (state.page && !state.page.isClosed()) {
        await state.page.close();
      }
    } catch (error) {
    }
    state.page = null;
    state.listenersAttached = false;
  }
  async function withPage(fn) {
    const page = await ensurePage();
    try {
      return await fn(page);
    } catch (error) {
      const message = error && error.message ? error.message.toLowerCase() : String(error).toLowerCase();
      if (message.includes("detached frame") || page.isClosed()) {
        pushLog("warn", "Page context detached; recreating and retrying once");
        await recreatePage();
        const page2 = await ensurePage();
        return await fn(page2);
      }
      throw error;
    }
  }
  const tools = [
    {
      name: "dev_list_permissions",
      description: "List available permissions and whether they are granted",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    {
      name: "dev_grant_permissions",
      description: "Grant one or more permissions after confirmation",
      inputSchema: {
        type: "object",
        properties: {
          permissions: {
            type: "array",
            items: { type: "string" },
            description: "Permissions to grant (use \"all\" or \"*\" for every permission)",
          },
          confirm: {
            type: "boolean",
            description: "Must be true to apply the changes",
          },
        },
        required: ["permissions", "confirm"],
      },
    },
    {
      name: "dev_revoke_permissions",
      description: "Revoke previously granted permissions",
      inputSchema: {
        type: "object",
        properties: {
          permissions: {
            type: "array",
            items: { type: "string" },
            description: "Permissions to revoke (use \"all\" or \"*\" for every permission)",
          },
          confirm: {
            type: "boolean",
            description: "Must be true to apply the changes",
          },
        },
        required: ["permissions", "confirm"],
      },
    },
    {
      name: "dev_status",
      description: "Report current URL, title, and log counts (requires permission: status)",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    {
      name: "dev_navigate",
      description: "Navigate the monitored page to the provided URL (requires permission: navigate)",
      inputSchema: {
        type: "object",
        properties: {
          url: { type: "string", description: "URL to open" },
        },
        required: ["url"],
      },
    },
    {
      name: "dev_reload",
      description: "Reload the monitored page and wait for network idle (requires permission: reload)",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    {
      name: "dev_screenshot",
      description: "Capture a screenshot of the monitored page (requires permission: screenshot)",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "Identifier for the screenshot" },
          fullPage: {
            type: "boolean",
            description: "Capture the full scrollable page when true",
          },
        },
        required: [],
      },
    },
    {
      name: "dev_clear_console",
      description: "Clear stored console log entries (requires permission: console_clear)",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    {
      name: "dev_eval",
      description: "Evaluate JavaScript in the monitored page (requires permission: eval)",
      inputSchema: {
        type: "object",
        properties: {
          expression: { type: "string", description: "JavaScript expression to evaluate" },
          awaitPromise: {
            type: "boolean",
            description: "Await returned promise values when true",
          },
        },
        required: ["expression"],
      },
    },
    {
      name: "dev_wait_for_selector",
      description: "Wait for a selector to appear (requires permission: wait_selector)",
      inputSchema: {
        type: "object",
        properties: {
          selector: { type: "string", description: "CSS selector to wait for" },
          timeout: {
            type: "number",
            description: "Timeout in milliseconds (defaults to MCP_WAIT_TIMEOUT)",
          },
          visible: {
            type: "boolean",
            description: "Require the element to be visible when true",
          },
          hidden: {
            type: "boolean",
            description: "Resolve when the element becomes hidden when true",
          },
        },
        required: ["selector"],
      },
    },
    {
      name: "dev_click_selector",
      description: "Click an element matching a selector (requires permission: click)",
      inputSchema: {
        type: "object",
        properties: {
          selector: { type: "string", description: "CSS selector of element to click" },
          button: {
            type: "string",
            enum: ["left", "middle", "right"],
            description: "Mouse button to use",
          },
          clickCount: {
            type: "number",
            description: "Number of click repetitions",
          },
          delay: {
            type: "number",
            description: "Delay between mousedown and mouseup in milliseconds",
          },
          timeout: {
            type: "number",
            description: "Timeout in milliseconds to find the element",
          },
        },
        required: ["selector"],
      },
    },
    {
      name: "dev_type_selector",
      description: "Type text into an element (requires permission: type)",
      inputSchema: {
        type: "object",
        properties: {
          selector: { type: "string", description: "CSS selector of input element" },
          text: { type: "string", description: "Text to type" },
          delay: {
            type: "number",
            description: "Delay between key presses in milliseconds",
          },
          clear: {
            type: "boolean",
            description: "Clear existing value before typing",
          },
          timeout: {
            type: "number",
            description: "Timeout in milliseconds to find the element",
          },
        },
        required: ["selector", "text"],
      },
    },
    {
      name: "dev_scroll",
      description: "Scroll the page viewport (requires permission: scroll)",
      inputSchema: {
        type: "object",
        properties: {
          x: {
            type: "number",
            description: "Horizontal scroll position in pixels",
          },
          y: {
            type: "number",
            description: "Vertical scroll position in pixels",
          },
          behavior: {
            type: "string",
            enum: ["auto", "smooth"],
            description: "Scroll behavior",
          },
        },
        required: [],
      },
    },
    {
      name: "dev_set_viewport",
      description: "Adjust the browser viewport (requires permission: set_viewport)",
      inputSchema: {
        type: "object",
        properties: {
          width: { type: "number", description: "Viewport width in pixels" },
          height: { type: "number", description: "Viewport height in pixels" },
          deviceScaleFactor: {
            type: "number",
            description: "Device scale factor to apply",
          },
        },
        required: ["width", "height"],
      },
    },
  ];
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const tool = request.params.name;
    const args = request.params.arguments ?? {};
    try {
      switch (tool) {
        case "dev_list_permissions": {
          const report = permissionStatusReport();
          const granted = Array.from(state.grantedPermissions).sort();
          const textResult = `${report}

Granted: ${granted.length ? granted.join(", ") : "(none)"}`;
          return {
            content: [{
              type: "text",
              text: textResult,
            }],
            isError: false,
          };
        }
        case "dev_grant_permissions": {
          const permissionsInput = args.permissions;
          if (!Array.isArray(permissionsInput) || permissionsInput.length === 0) {
            throw new Error('Argument "permissions" must be a non-empty array.');
          }
          if (args.confirm !== true) {
            throw new Error('Set "confirm" to true to grant permissions.');
          }
          const permissions = expandPermissionInputs(permissionsInput);
          const granted = [];
          const already = [];
          for (const name of permissions) {
            if (hasPermission(name)) {
              already.push(name);
            } else {
              state.grantedPermissions.add(name);
              granted.push(name);
            }
          }
          const message = [
            granted.length ? `Granted: ${granted.join(", ")}` : null,
            already.length ? `Already granted: ${already.join(", ")}` : null,
          ]
            .filter(Boolean)
            .join("\n") || "No changes";
          return {
            content: [{ type: "text", text: message }],
            isError: false,
          };
        }
        case "dev_revoke_permissions": {
          const permissionsInput = args.permissions;
          if (!Array.isArray(permissionsInput) || permissionsInput.length === 0) {
            throw new Error('Argument "permissions" must be a non-empty array.');
          }
          if (args.confirm !== true) {
            throw new Error('Set "confirm" to true to revoke permissions.');
          }
          const permissions = expandPermissionInputs(permissionsInput);
          const revoked = [];
          const missing = [];
          for (const name of permissions) {
            if (hasPermission(name)) {
              state.grantedPermissions.delete(name);
              revoked.push(name);
            } else {
              missing.push(name);
            }
          }
          const message = [
            revoked.length ? `Revoked: ${revoked.join(", ")}` : null,
            missing.length ? `Not granted: ${missing.join(", ")}` : null,
          ]
            .filter(Boolean)
            .join("\n") || "No changes";
          return {
            content: [{ type: "text", text: message }],
            isError: false,
          };
        }
        case "dev_status": {
          requirePermission("status");
          const { title, url } = await withPage(async (page) => ({ title: await page.title(), url: page.url() }));
          return {
            content: [{
              type: "text",
              text: `URL: ${url}
Title: ${title}
Console entries: ${state.consoleLogs.length}
Screenshots stored: ${state.screenshots.size}`,
            }],
            isError: false,
          };
        }
        case "dev_navigate": {
          requirePermission("navigate");
          if (!args.url || typeof args.url !== "string") {
            throw new Error("Missing required argument: url");
          }
          await withPage(async (page) => page.goto(args.url, { waitUntil: "networkidle0", timeout: NAV_TIMEOUT }));
          pushLog("info", `Navigated to ${args.url}`);
          return {
            content: [{ type: "text", text: `Navigated to ${args.url}` }],
            isError: false,
          };
        }
        case "dev_reload": {
          requirePermission("reload");
          await withPage(async (page) => page.reload({ waitUntil: "networkidle0", timeout: NAV_TIMEOUT }));
          pushLog("info", "Reloaded monitored page");
          return {
            content: [{ type: "text", text: "Reloaded page" }],
            isError: false,
          };
        }
        case "dev_screenshot": {
          requirePermission("screenshot");
          const name = typeof args.name === "string" && args.name.trim().length > 0
            ? args.name.trim()
            : `screenshot-${Date.now()}`;
          const buffer = await withPage(async (page) => page.screenshot({
            type: "png",
            fullPage: Boolean(args.fullPage),
          }));
          if (state.screenshots.size >= MAX_SCREENSHOTS) {
            const firstKey = state.screenshots.keys().next().value;
            state.screenshots.delete(firstKey);
          }
          state.screenshots.set(name, buffer);
          notifyScreenshotUpdate();
          pushLog("info", `Captured screenshot ${name}`);
          return {
            content: [{ type: "text", text: `Captured screenshot ${name}` }],
            isError: false,
          };
        }
        case "dev_clear_console": {
          requirePermission("console_clear");
          state.consoleLogs.length = 0;
          notifyConsoleUpdate();
          return {
            content: [{ type: "text", text: "Cleared console log buffer" }],
            isError: false,
          };
        }
        case "dev_eval": {
          requirePermission("eval");
          if (typeof args.expression !== "string" || !args.expression.trim()) {
            throw new Error('Argument "expression" must be a non-empty string.');
          }
          const expression = args.expression.trim();
          const awaitPromise = Boolean(args.awaitPromise);
          const result = await withPage(async (page) =>
            page.evaluate(
              ({ code, wait }) => {
                const runner = new Function(`return (${code});`);
                const value = runner();
                if (wait && value && typeof value.then === "function") {
                  return value;
                }
                if (!wait && value && typeof value.then === "function") {
                  return "[Promise]";
                }
                return value;
              },
              { code: expression, wait: awaitPromise },
            ),
          );
          return {
            content: [{ type: "text", text: serializeForOutput(result) }],
            isError: false,
          };
        }
        case "dev_wait_for_selector": {
          requirePermission("wait_selector");
          if (typeof args.selector !== "string" || !args.selector.trim()) {
            throw new Error('Argument "selector" must be a non-empty string.');
          }
          const selector = args.selector.trim();
          const timeout = args.timeout !== undefined ? Number(args.timeout) : WAIT_TIMEOUT;
          if (!Number.isFinite(timeout) || timeout <= 0) {
            throw new Error('Argument "timeout" must be a positive number when provided.');
          }
          const visible = args.visible === undefined ? undefined : Boolean(args.visible);
          const hidden = args.hidden === undefined ? undefined : Boolean(args.hidden);
          if (visible && hidden) {
            throw new Error('Arguments "visible" and "hidden" cannot both be true.');
          }
          await withPage(async (page) => {
            const options = { timeout };
            if (visible !== undefined) {
              options.visible = visible;
            }
            if (hidden !== undefined) {
              options.hidden = hidden;
            }
            await page.waitForSelector(selector, options);
          });
          return {
            content: [{ type: "text", text: `Selector "${selector}" satisfied` }],
            isError: false,
          };
        }
        case "dev_click_selector": {
          requirePermission("click");
          if (typeof args.selector !== "string" || !args.selector.trim()) {
            throw new Error('Argument "selector" must be a non-empty string.');
          }
          const selector = args.selector.trim();
          const timeout = args.timeout !== undefined ? Number(args.timeout) : WAIT_TIMEOUT;
          if (!Number.isFinite(timeout) || timeout <= 0) {
            throw new Error('Argument "timeout" must be a positive number when provided.');
          }
          const button = typeof args.button === "string" ? args.button : "left";
          if (!["left", "middle", "right"].includes(button)) {
            throw new Error('Argument "button" must be one of: left, middle, right.');
          }
          const clickCount = args.clickCount !== undefined ? Number(args.clickCount) : 1;
          if (!Number.isFinite(clickCount) || clickCount <= 0) {
            throw new Error('Argument "clickCount" must be a positive number when provided.');
          }
          const delay = args.delay !== undefined ? Number(args.delay) : undefined;
          if (delay !== undefined && (!Number.isFinite(delay) || delay < 0)) {
            throw new Error('Argument "delay" must be a non-negative number when provided.');
          }
          await withPage(async (page) => {
            await page.waitForSelector(selector, { timeout });
            const clickOptions = {
              button,
              clickCount: Math.round(clickCount),
            };
            if (delay !== undefined) {
              clickOptions.delay = delay;
            }
            await page.click(selector, clickOptions);
          });
          return {
            content: [{ type: "text", text: `Clicked selector "${selector}"` }],
            isError: false,
          };
        }
        case "dev_type_selector": {
          requirePermission("type");
          if (typeof args.selector !== "string" || !args.selector.trim()) {
            throw new Error('Argument "selector" must be a non-empty string.');
          }
          if (typeof args.text !== "string") {
            throw new Error('Argument "text" must be a string.');
          }
          const selector = args.selector.trim();
          const textValue = args.text;
          const timeout = args.timeout !== undefined ? Number(args.timeout) : WAIT_TIMEOUT;
          if (!Number.isFinite(timeout) || timeout <= 0) {
            throw new Error('Argument "timeout" must be a positive number when provided.');
          }
          const delay = args.delay !== undefined ? Number(args.delay) : undefined;
          if (delay !== undefined && (!Number.isFinite(delay) || delay < 0)) {
            throw new Error('Argument "delay" must be a non-negative number when provided.');
          }
          const clear = Boolean(args.clear);
          await withPage(async (page) => {
            await page.waitForSelector(selector, { timeout });
            if (clear) {
              await page.$eval(
                selector,
                (element) => {
                  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
                    element.value = "";
                  } else {
                    element.textContent = "";
                  }
                },
              );
            }
            if (delay !== undefined) {
              await page.type(selector, textValue, { delay });
            } else {
              await page.type(selector, textValue);
            }
          });
          return {
            content: [{ type: "text", text: `Typed into selector "${selector}"` }],
            isError: false,
          };
        }
        case "dev_scroll": {
          requirePermission("scroll");
          const hasX = args.x !== undefined;
          const hasY = args.y !== undefined;
          const x = hasX ? Number(args.x) : undefined;
          const y = hasY ? Number(args.y) : undefined;
          if (hasX && !Number.isFinite(x)) {
            throw new Error('Argument "x" must be a finite number when provided.');
          }
          if (hasY && !Number.isFinite(y)) {
            throw new Error('Argument "y" must be a finite number when provided.');
          }
          const behavior = typeof args.behavior === "string" ? args.behavior : "auto";
          if (!["auto", "smooth"].includes(behavior)) {
            throw new Error('Argument "behavior" must be "auto" or "smooth" when provided.');
          }
          await withPage(async (page) =>
            page.evaluate(
              ({ left, top, scrollBehavior }) => {
                const options = { behavior: scrollBehavior };
                if (left !== undefined) {
                  options.left = left;
                }
                if (top !== undefined) {
                  options.top = top;
                }
                window.scrollTo(options);
              },
              {
                left: hasX ? x : undefined,
                top: hasY ? y : undefined,
                scrollBehavior: behavior,
              },
            ),
          );
          return {
            content: [{ type: "text", text: "Scrolled page" }],
            isError: false,
          };
        }
        case "dev_set_viewport": {
          requirePermission("set_viewport");
          const width = Number(args.width);
          const height = Number(args.height);
          if (!Number.isFinite(width) || width <= 0) {
            throw new Error('Argument "width" must be a positive number.');
          }
          if (!Number.isFinite(height) || height <= 0) {
            throw new Error('Argument "height" must be a positive number.');
          }
          const dsfProvided = args.deviceScaleFactor !== undefined;
          const deviceScaleFactor = dsfProvided ? Number(args.deviceScaleFactor) : undefined;
          if (dsfProvided && (!Number.isFinite(deviceScaleFactor) || deviceScaleFactor <= 0)) {
            throw new Error('Argument "deviceScaleFactor" must be a positive number when provided.');
          }
          await withPage(async (page) =>
            page.setViewport({
              width: Math.round(width),
              height: Math.round(height),
              deviceScaleFactor: dsfProvided ? deviceScaleFactor : undefined,
            }),
          );
          const suffix = dsfProvided ? ` @${deviceScaleFactor}x` : "";
          return {
            content: [{ type: "text", text: `Viewport set to ${Math.round(width)}x${Math.round(height)}${suffix}` }],
            isError: false,
          };
        }
        default:
          return {
            content: [{ type: "text", text: `Unknown tool: ${tool}` }],
            isError: true,
          };
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      pushLog("error", `Tool ${tool} failed: ${message}`);
      return {
        content: [{ type: "text", text: message }],
        isError: true,
      };
    }
  });
}

async function shutdown() {
  if (state.browser) {
    try {
      await state.browser.close();
    } catch (error) {
      // ignore shutdown errors
    }
    state.browser = null;
    state.page = null;
    state.listenersAttached = false;
  }
}

async function main() {
  registerHandlers();
  process.stdin.on("close", shutdown);
  process.on("SIGINT", async () => {
    await shutdown();
    process.exit(0);
  });
  process.on("SIGTERM", async () => {
    await shutdown();
    process.exit(0);
  });
  const transport = new StdioServerTransport();
  await server.connect(transport);
  await autoNavigate();
}

main().catch(async (error) => {
  console.error("Failed to start dev chrome monitor server", error);
  await shutdown();
  process.exit(1);
});


